/**
 *
 */
package org.windowkit.toolkit.widget.tree;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;

import org.apache.commons.lang.StringUtils;

/**
 * SDefaultTreeNode extends DefaultMutableTreeNode for use with ****;
 *
 * @author Eric Lundin
 *
 */
public class BaseDefaultMutableTreeNode extends DefaultMutableTreeNode implements
		Serializable, Comparable {

	/**
	 *
	 */
	private static final long serialVersionUID = -6983996108562934038L;

	private static final Comparator SDEFAULT_MUTABLE_TREE_NODE_COMAPRATOR = new Comparator<Comparable>() {
		public int compare(final Comparable object1, final Comparable object2) {
			return object1.compareTo(object2);
		}
	};

	// The data to associate with this node.
	ArrayList<Object> userObject;

	// This node's parent or null if it does not have a parent.
	BaseDefaultMutableTreeNode parent;

	// The name to display to represent this node.
	String displayValue;

	// Store whether to sort children based on whether they are branches or
	// leaves.
	private boolean autoArrange;

	/**
	 * Create a tree node which does not have any children. By default this node
	 * will sort its children based on whether they are branches or leaves.
	 *
	 * @param displayName
	 *            The text to display for this node.
	 * @param data
	 *            The data to back this node with.
	 * @param parent
	 *            The parent of this node.
	 * @param allowsChildren
	 *            Whether this node allows children.
	 */
	public BaseDefaultMutableTreeNode(final String displayName, final Collection data,
			final MutableTreeNode parent, final boolean allowsChildren) {
		super(parent, allowsChildren);
		init(displayName, data, null, parent);
	}

	/**
	 * Create a tree node. By default this node will sort its children based on
	 * whether they are branches or leaves.
	 *
	 * @param displayName
	 *            The text to display for this node.
	 * @param data
	 *            The data to back this node with.
	 * @param children
	 *            A collection of children.
	 * @param parent
	 *            The parent of this node.
	 */
	public BaseDefaultMutableTreeNode(final String displayName, final Collection data,
			final Collection< ? extends DefaultMutableTreeNode> children,
			final MutableTreeNode parent) {
		super(parent, true);
		init(displayName, data, children, parent);
	}

	/**
	 * Initialize this node by setting references to parent/children.
	 */
	private void init(final String displayName, final Collection data,
			final Collection< ? extends DefaultMutableTreeNode> children,
			final MutableTreeNode parent) {
		this.autoArrange = true; // TODO MOVE THIS SOMEWHERE ELSE
		this.displayValue = displayName;
		addChildren(children);
		userObject = new ArrayList<Object>();
		setParent(parent);
		setUserObject(data);
		if (children != null) {
			for (DefaultMutableTreeNode current : children) {
				if (current.getParent() == null) {
					current.setParent(this);
				}
			}
		}
	}

	public MutableTreeNode cloneNoParent() {
		DefaultMutableTreeNode newNode = null;
		newNode = (DefaultMutableTreeNode) super.clone();
		newNode.setAllowsChildren(getAllowsChildren());
		for (int i = 0; i < getChildCount(); i++) {
			newNode.add((BaseDefaultMutableTreeNode) getChildAt(i));
		}
		return newNode;
	}

	public BaseDefaultMutableTreeNode fullClone() {
		BaseDefaultMutableTreeNode newNode;
		if (getAllowsChildren()) {
			newNode = new BaseDefaultMutableTreeNode(getDisplayValue(),
					getUserObjectCollection(), getChildren(),
					(BaseDefaultMutableTreeNode) getParent());
		}
		else {
			newNode = new BaseDefaultMutableTreeNode(getDisplayValue(),
					getUserObjectCollection(),
					(BaseDefaultMutableTreeNode) getParent(), false);
		}
		newNode.setParent((MutableTreeNode) getParent());
		newNode.addChildren(getChildren());
		newNode.setAllowsChildren(getAllowsChildren());
		return newNode;
	}

	public BaseDefaultMutableTreeNode cloneNoChildren() {
		BaseDefaultMutableTreeNode newNode = null;
		newNode = (BaseDefaultMutableTreeNode) super.clone();
		newNode.setParent((MutableTreeNode) getParent());
		newNode.setAllowsChildren(this.getAllowsChildren());
		return newNode;
	}

	/**
	 * Adds the childrren to this node. If auto arrange children is on children
	 * will be sorted in order by whether they allow children.
	 *
	 * @param children
	 */
	public void addChildren(
			final Collection< ? extends DefaultMutableTreeNode> children) {
		if (children == null) {
			return;
		}
		for (DefaultMutableTreeNode currentNode : children) {
			add(currentNode);
		}
	}

	/**
	 * Sorts the children of this node based on whether they are branches or
	 * leaves, then in alphabetical/numerical order.
	 *
	 * @return The sorted children.
	 */
	public void sortChildren() {
		Collections.sort(children, SDEFAULT_MUTABLE_TREE_NODE_COMAPRATOR);
	}

	public ArrayList<BaseDefaultMutableTreeNode> getChildren() {
		Enumeration children = children();
		ArrayList<BaseDefaultMutableTreeNode> childrenCollection = new ArrayList<BaseDefaultMutableTreeNode>();
		while (children.hasMoreElements()) {
			childrenCollection.add((BaseDefaultMutableTreeNode) children
					.nextElement());
		}
		return childrenCollection;
	}

	/**
	 * Returns the text to display for this node in the view. Called from
	 * Tree.convertValueToText
	 *
	 * @see Tree.convertValueToText(Object value, boolean selected, boolean
	 *      expanded, boolean leaf, int row, boolean hasFocus)</code>
	 *
	 * @return The string to display to represent this view node.
	 */
	public String getDisplayValue() {
		return displayValue;
	}

	/**
	 * Get the index of the given tree node in relation to this node.
	 */
	@Override
	public int getIndex(final TreeNode node) {
		return children.indexOf(node);

	}

	public ArrayList<Object> getUserObjectCollection() {
		return userObject;
	}

	/**
	 * Set the user object to the given object.
	 *
	 * @see BaseDefaultMutableTreeNode#setUserObject(Object[])
	 */
	@Override
	public void setUserObject(final Object object) {
		userObject.clear();
		userObject.add(object);
	}

	/**
	 * Sets the user object to the given array.
	 *
	 * @param object
	 *            The array of objects to store.
	 */
	public void setUserObject(final Object[] object) {
		this.userObject.clear();
		for (Object currentObject : object) {
			userObject.add(currentObject);
		}
	}

	/**
	 * Set whether to arrange nodes based on whether they are branches or
	 * leaves.
	 *
	 * @param autoArrange
	 *            Whether to arrange the children or leave them in the order the
	 *            developer added them in.
	 */
	public void setAutoArrange(final boolean autoArrange) {
		this.autoArrange = autoArrange;
	}

	/**
	 * @param displayValue
	 */
	public void setDisplayValue(final String displayValue) {
		this.displayValue = displayValue;

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Comparable#compareTo(T)
	 */
	public int compareTo(final Object o) {
		BaseDefaultMutableTreeNode otherNode = (BaseDefaultMutableTreeNode) o;
		if (isLeaf() && !otherNode.isLeaf()) {
			return 1;
		}
		else if (!isLeaf() && otherNode.isLeaf()) {
			return -1;
		}
		else {
			Character c1;
			Character c2;
			String s1 = getDisplayValue();
			String s2 = otherNode.getDisplayValue();
			if (s1.length() < s2.length()) {
				return -1;
			}
			if (s1.length() > s2.length()) {
				return 1;
			}

			for (int i = 0; i < Math.min(s1.length(), s2.length()); i++) {
				if ((c1 = s1.charAt(i)) != (c2 = s2.charAt(i))) {
					if (StringUtils.isNumeric(c1.toString())
							&& StringUtils.isAlpha(c2.toString())) {
						return -1;
					}
					if (StringUtils.isAlpha(c1.toString())
							&& StringUtils.isNumeric(c2.toString())) {
						return 1;
					}
					if (!StringUtils.isAlpha(c1.toString())
							&& StringUtils.isAlpha(c2.toString())) {
						if (c1 < c2) {
							return -1;
						}
						if (c1 > c2) {
							return 1;
						}
					}
					if (StringUtils.isNumeric(c1.toString())
							&& StringUtils.isNumeric(c2.toString())) {
						return Integer.valueOf(c1.toString()).compareTo(
								Integer.valueOf(c2.toString()));
					}
				}
			}
			return 0;
		}
	}
}
